/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define LOG_TAG "MAIN"

#include "include.h"
#include "global.h"
#include "def.h"
#include "util.h"
#include "log.h"
#include "loop.h"
#include "daemon.h"
#include "version.h"

#define PID_FILE "/tmp/wfs.pid"

/** Stores user input parameters */
struct wfs_input_params {
	int background;
	char *log_file;
};

/***********************************************************
 *                    Local variables
 ***********************************************************
 */
static const char *wfs_version =
"WFS v" VERSION "\n"
"Copyright (c) <2012>, Yang Yang <cntototo@gmail.com>";

static const char *wfs_license =
"This software may be distributed under the terms of the New BSD License.\n"
"See README for more details.";

static const char *wfs_usage =
"usage:\n"
"  wfs [-Bhv] [-l<log_file>]";

static const char *wfs_options =
"options:\n"
"  -B = run daemon in background\n"
"  -d = increase debug level by 1\n"
"  -h = show help information\n"
"  -l = output stdout and stderr to log file\n"
"  -v = show version";

static const char *wfs_example =
"example:\n"
"  wfs"; /* TODO: example string */


/***********************************************************
 *                    Local functions
 ***********************************************************
 */
static void usage(void)
{
	/** Vesion and license */
	printf("%s\n\n%s\n\n", wfs_version, wfs_license);
	/** Usage */
	printf("%s\n\n", wfs_usage);
	/** Option */
	printf("%s\n\n", wfs_options);
	/** Example */
	printf("%s\n", wfs_example);
}


static struct wfs_global *wfs_parse_params(struct wfs_input_params *params)
{
	struct wfs_global *global = NULL;

	assert(params);

	global = (struct wfs_global *) zalloc(sizeof(*global));
	if (!global) {
		wfs_printf(LOG_ERR, "%s", err_to_str(WFS_INTERNAL_ERR_OOM));
		return NULL;
	}

	global->background = params->background;
	global->log_file = params->log_file;

	return global;
}


static void signal_terminate_handler(int sig, void *ctx)
{
	assert(ctx);

	/** TODO: handle SIGALRM */

	wfs_loop_terminate();
}


static void wfs_deinit(struct wfs_global *global)
{
	assert(global);

	if (wfs_daemon_deinit(global))
		return;

	wfs_loop_deinit();
}


static enum wfs_internal_err wfs_init(struct wfs_global *global)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(global);

	ret = wfs_loop_init();
	if (ret)
		goto out;

	ret = wfs_daemon_init(global);
	if (ret)
		goto out;

	wfs_loop_register_sig(SIGHUP, signal_terminate_handler, global);
	wfs_loop_register_sig(SIGINT, signal_terminate_handler, global);
	wfs_loop_register_sig(SIGTERM, signal_terminate_handler, global);

out:
	if (ret) {
		wfs_printf(LOG_ERR, "Failed to initialize WFS.");
		wfs_deinit(global);
	}
	return ret;
}


static int redirect_to_log(const char *log_file)
{
	int ret = FALSE;
	int devnull_fd = 0;
	int log_fd = 0;

	devnull_fd = open("/dev/null", O_RDWR);
	if (devnull_fd < 0) {
		wfs_perror("[devnull] open");
		goto out;
	}

	if (log_file)
		log_fd = open(log_file, O_RDWR | O_CREAT, 0640);
	else
		log_fd = devnull_fd;
	if (log_fd < 0) {
		wfs_perror("[log_fd] open");
		goto out;
	}

	if (dup2(devnull_fd, STDIN_FILENO) < 0) {
		wfs_perror("[stdin] dup2");
		goto out;
	}
	if (dup2(log_fd, STDOUT_FILENO) < 0) {
		wfs_perror("[stdout] dup2");
		goto out;
	}
	if (dup2(log_fd, STDERR_FILENO) < 0) {
		wfs_perror("[stderr] dup2");
		goto out;
	}

	ret = TRUE;
out:
	if (ret != TRUE)
		wfs_printf(LOG_ERR, "Failed to redirect stdin, stdout, "
				"and stderr.");
	CLOSE(devnull_fd);
	CLOSE(log_fd);
	return ret;
}


/***********************************************************
 *                    Main entrance
 ***********************************************************
 */
int main(int argc, char *argv[])
{
	int exitcode = 0;
	int ret = 0;
	int opt = 0;
	int lock_fd = 0;
	struct wfs_input_params params;
	struct wfs_global *global = NULL;
	char buf[128]; /* for pid file */
	int log_level = DEFAULT_LOG;
	int d_cnt = 0;

	memset(&params, 0, sizeof(params));
	memset(buf, 0, 128);

	lock_fd = open(PID_FILE, O_RDWR | O_CREAT, 0640);
	if (lock_fd < 0) {
		wfs_perror("[PID_FILE] open");
		exitcode = -1;
		goto out;
	}
	ret = lockf(lock_fd, F_TEST, 0);
	if (ret != 0) {
		if (errno == EACCES || errno == EAGAIN)
			wfs_printf(LOG_ERR, "PID file %s is locked. Please "
				"remove it if WFS is not running.", PID_FILE);
		wfs_perror("lockf");
		exitcode = -1;
		goto out;
	}

	for (;;) {
		opt = getopt(argc, argv, "Bdhl:v");
		if (opt < 0)
			break;

		switch (opt) {
		case 'B':
			params.background = 1;
			break;
		case 'd':
#ifdef CONFIG_NO_STDOUT_DEBUG
			if (d_cnt)
				break;
			wfs_printf(LOG_INFO, "Debug is disabled by "
				"CONFIG_NO_STDOUT_DEBUG=y defconfig option.");
#else
			log_level |= LOG_DEBUG << d_cnt;
#endif
			++d_cnt;
			break;
		case 'h':
			usage();
			goto out;
		case 'l':
			params.log_file = optarg;
			break;
		case 'v':
			printf("%s\n", wfs_version);
			goto out;
		default:
			usage();
			goto out;
		}
	}

	set_log_level(log_level);

	global = wfs_parse_params(&params);
	if (!global) {
		wfs_printf(LOG_ERR, "Failed to parse input parameters.");
		exitcode = -1;
		goto out;
	}

	if (global->background) {
		ret = fork();
		if (ret < 0) {
			wfs_perror("fork");
			exitcode = -1;
			goto out;
		} else if (ret > 0) {
			/** Parent process */
			goto out;
		} else {
			/** Child process */
			setsid();
		}
	}

	if (global->background || global->log_file) {
		if (!redirect_to_log(global->log_file)) {
			exitcode = -1;
			goto out;
		}
	}

	umask(0027);
	ret = lockf(lock_fd, F_TLOCK, 0);
	if (ret != 0) {
		wfs_perror("lockf");
		exitcode = -1;
		goto out;
	}

	sprintf(buf, "%u\n", getpid());
	ret = write(lock_fd, buf, strlen(buf));
	if (ret < 0) {
		wfs_perror("write");
		exitcode = -1;
		goto out;
	}

	if (wfs_init(global)) {
		exitcode = -1;
		goto out;
	}

	wfs_printf(LOG_INFO, "WFS is started%ssuccessfully.",
			(global->background) ? " in background " : " ");

	wfs_loop_run();

	wfs_deinit(global);

out:
	CLOSE(STDIN_FILENO);
	CLOSE(STDOUT_FILENO);
	CLOSE(STDERR_FILENO);
	FREE(global);
	CLOSE(lock_fd);
	return exitcode;
}
